#include "mytensorflow.h"
#include "../factory/factory.h"
#include "../factory/constant_factory.h"
#include "../factory/variable_factory.h"
#include "../factory/placeholder_factory.h"
#include "../factory/add_factory.h"
#include "../factory/subtract_factory.h"
#include "../factory/multiply_factory.h"
#include "../factory/divide_factory.h"
#include "../factory/pow_factory.h"
#include "../factory/log_factory.h"
#include "../factory/exp_factory.h"
#include "../factory/sum_factory.h"
#include "../factory/matmul_factory.h"
#include "../factory/sigmoid_factory.h"
#include "../factory/gradient_descent_factory.h"

namespace mytensorflow{
    const NodeWrapper operator+(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory * factory = new AddFactory(lhs.getNode(), rhs.getNode());
        Node* node = factory -> createNode();
        delete factory;
        return NodeWrapper(node);
    }

    const NodeWrapper operator+(const NodeWrapper & node, const double num){
        Factory* factory;
        Shape* shape = node.getNode() -> getOutShape();
        factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new AddFactory(num_node, node.getNode());
        Node* add_node = factory -> createNode();
        delete factory;
        return NodeWrapper(add_node);
    }

    const NodeWrapper operator+(const double num, const NodeWrapper & node){
        return node + num;
    }

    const NodeWrapper operator-(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory* factory;
        factory = new SubtractFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator-(const NodeWrapper & node, const double num){
        Factory* factory;
        Shape* shape = node.getNode() -> getOutShape();
        factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new SubtractFactory(node.getNode(), num_node);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator-(const double num, const NodeWrapper & node){
        Factory* factory;
        Shape* shape = node.getNode() -> getOutShape();
        factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new SubtractFactory(num_node, node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator*(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory* factory = new MultiplyFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory; 
        return NodeWrapper(ret);
    }

    const NodeWrapper operator*(const NodeWrapper & node, const double num){
        Shape* shape = node.getNode() -> getOutShape();
        Factory* factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new MultiplyFactory(num_node, node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator*(const double num, const NodeWrapper & node){
        return node * num;
    }

    const NodeWrapper operator/(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory* factory = new DivideFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator/(const NodeWrapper & node, const double num){
        Shape* shape = node.getNode() -> getOutShape();
        Factory* factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new DivideFactory(node.getNode(), num_node);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator/(const double num, const NodeWrapper & node){
        Shape* shape = node.getNode() -> getOutShape();
        Factory* factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, num));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new DivideFactory(num_node, node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    const NodeWrapper operator-(const NodeWrapper & node){
        Factory* factory;
        Shape* shape = node.getNode() -> getOutShape();
        factory = new ConstantFactory(Matrix(shape -> rows, shape -> cols, -1));
        Node* num_node = factory -> createNode();
        delete factory;
        factory = new MultiplyFactory(num_node, node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper constant(const Matrix & value){
        Factory* factory = new ConstantFactory(value);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper variable(const Matrix & value){
        Factory* factory = new VariableFactory(value);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper placeholder(const Shape & shape){
        Factory* factory = new PlaceholderFactory(shape);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }
    
    NodeWrapper sum(const NodeWrapper & node){
        Factory* factory = new SumFactory(node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }
    
    NodeWrapper exp(const NodeWrapper & node){
        Factory* factory = new ExpFactory(node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper log(const NodeWrapper & node){
        Factory* factory = new LogFactory(node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper matmul(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory* factory = new MatmulFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }
    
    NodeWrapper sigmoid(const NodeWrapper & node){
        Factory* factory = new SigmoidFactory(node.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper pow(const NodeWrapper & lhs, const NodeWrapper & rhs){
        Factory* factory = new PowFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper multiply(const NodeWrapper & lhs, const NodeWrapper & rhs) {
        Factory* factory = new MultiplyFactory(lhs.getNode(), rhs.getNode());
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }

    NodeWrapper gradient_descent_optimizer(const NodeWrapper& node, double alpha) {
        Factory* factory = new GradientDescentFactory(node.getNode(), alpha);
        Node* ret = factory -> createNode();
        delete factory;
        return NodeWrapper(ret);
    }
}
